﻿/** * This code is part of the Bumpslide Library by David Knape * http://bumpslide.com/ *  * Copyright (c) 2006, 2007, 2008 by Bumpslide, Inc. *  * Released under the open-source MIT license. * http://www.opensource.org/licenses/mit-license.php * see LICENSE.txt for full license terms */  package com.bumpslide.ui {	import com.bumpslide.data.Binding;	import com.bumpslide.data.IBindable;	import com.bumpslide.events.ModelChangeEvent;	import com.bumpslide.ui.BaseClip;	import com.bumpslide.ui.IResizable;		import flash.display.DisplayObject;	import flash.events.Event;	import flash.utils.getTimer;		/**	 * Universal Base Class for UI Components	 * 	 * This class extends BaseClip by adding validation/update routines as well as	 * a basic IResizable and IBindable implementation.	 * 	 * Inspiration from Keith Peters' MinimalComps for AS3.	 * 		 * @author David Knape	 */	 		// MXML Default property	[DefaultProperty("children")]
	public class Component extends BaseClip implements IResizable, IBindable {
		// notifies parents when we've been redrawn		[Event(name='onComponentRedraw',type='com.bumpslide.events.UIEvent')]		public static const EVENT_DRAW:String = "onComponentRedraw";
				// whether or not to wait a frame before drawing updates		// (originally a timer delay, thus the number) 		public var updateDelay:Number = 1;
				// whether or not to round x/y coordinates automatically		public var roundedPosition:Boolean = true;
		// change types (aka. validation constants) 		protected static const VALID_SIZE:String = "validSize";  // size has changed		protected static const VALID_DATA:String = "validData";  // data has changed
		// map of invalidation flags		private var _invalidated:Array;
		// how big we think we are		protected var _width:Number = -1;		protected var _height:Number = -1;
		
		/**		 * Initialize size and scale, calls addChildren and triggers validation		 */		override protected function init():void {						super.init();						// hack for components that are rotated on the stage			// (only handles those rotated by increments of 90 degrees)			// to account for issues related to height/width of rotated components			var rot:Number = (rotation < 0) ? rotation + 360 : rotation;			var rotated:Boolean = ( Math.round((rot) / 90) * 90 % 180 ) == 90;											// use placed height/width by default and reset scale to 100%			if(_width == -1) {								_width = rotated ? super.height : super.width;				invalidate(VALID_SIZE);			}			if(_height == -1) {				_height = rotated ? super.width : super.height;				invalidate(VALID_SIZE);			}			scaleX = scaleY = 1;        				// init hook for subclasses to add children and such     			addChildren();									addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);						invalidate();		}				
		/**		 * Removes all listeners, prepares for destruction		 */		override public function destroy():void {			super.destroy();			removeEventListener(Event.ENTER_FRAME, updateNow);			}		
		/**		 * Adds and Initializes Child Components		 * 		 * This method should be overriden in your subclass.		 */		protected function addChildren():void {					}
		/**		 * Updates the  Display List 		 * 		 * This method should be overriden in your subclass.  Be sure to call 		 * super.draw() if you want draw events dispatched.		 * 		 * Note, that we validate our two known validaiton constants here for 		 * the sake of shortcut.  Again, just be sure to call super.draw() at		 * the END of your draw routine.		 */		protected function draw():void {			validate(VALID_SIZE);			validate(VALID_DATA);			notifyDrawn();		}
		/**		 * Send the EVENT_DRAW UIEvent		 */		protected function notifyDrawn(event:Event = null):void {			removeEventListener(Event.ENTER_FRAME, notifyDrawn);			sendEvent(Component.EVENT_DRAW);		}
		/**		 * Triggers an update (after a slight delay)		 */		protected function invalidate(changeType:String = null):void {			if(changeType != null) {				// mark this change type as invalid				if(_invalidated==null) _invalidated = new Array();				_invalidated[changeType] = true;			}			if(updateDelay) {				addEventListener(Event.ENTER_FRAME, updateNow);			} else {				updateNow();			}		}
		/**		 * Whether or not a given change type (validation constant) has changed		 */		protected function hasChanged( changeType:String ):Boolean {			if(_invalidated==null) return false;			else return _invalidated[changeType] != null;		}
		/**		 * Marks a change type (validation constant) as valid		 */		protected function validate( changeType:String):void {			if(_invalidated!=null) {				_invalidated[changeType] = null;				delete _invalidated[changeType];			}		}
		/**		 * Force instant update by calling draw(), and cancels any pending update		 */		public function updateNow( e:Event = null ):void {			removeEventListener(Event.ENTER_FRAME, updateNow);						draw();		}
		/**		 * Moves the component to the specified position.		 * 		 * @param xpos the x position to move the component		 * @param ypos the y position to move the component		 */		public function move(xpos:Number, ypos:Number):void {			x = xpos;			y = ypos;		}
		//------------------------------//		// IResizable Implementation    //		//------------------------------//				/**		 * Sets the size of the component and triggers instant redraw if size needs to change. 		 * 		 * @param w The width of the component.		 * @param h The height of the component.		 */		public function setSize( w:Number, h:Number ):void {			width = w;			height = h;				if(hasChanged(VALID_SIZE)) updateNow();		}
		//------------------------------//		// IBindable Implementation     //		//------------------------------//				/**		 * Bind to a property on this component (only certain properties are bindable)		 */		public function bind( property:String, target:Object, setterOrFunction:*= null ):Binding {			return Binding.create(this, property, target, setterOrFunction);		}		/**		 * Remove all bindings from this component to the target object		 */		public function unbind( target:Object ):void {			Binding.remove(target);		}		/**		 * Dispatch ModelChangeEvent for a property to execute binding		 */		protected function sendChangeEvent( propertyName:String, value:*, oldValue:*= null):void {			dispatchEvent(new ModelChangeEvent(this, propertyName, value, oldValue));		}		//------------------------------//		// GETTERS / SETTERS            //		//------------------------------//				/**		 * Overrides the setter for x to always place the component on a whole pixel		 * 		 * This behavior can be disabled by setting the 'roundedPosition' property to false		 */		override public function set x(value:Number):void {			super.x = roundedPosition ? Math.round(value) : value;		}		/**		 * Overrides the setter for y to always place the component on a whole pixel.		 * 		 * This behavior can be disabled by setting the 'roundedPosition' property to false		 */		override public function set y(value:Number):void {			super.y = roundedPosition ? Math.round(value) : value;		}		/**		 * Overrides the 'enabled' setter to also enable mouse access		 * to children.  Note, that the Button class has it's own implementation		 * that works a little differently.		 */		override public function set enabled( isEnabled:Boolean ):void {			super.enabled = isEnabled;			mouseEnabled = isEnabled;			// let buttons handle their own mouse children			if(!(this is Button)) mouseChildren = isEnabled;     			sendChangeEvent('enabled', isEnabled, !isEnabled);			invalidate();		}		/**		 * Sets/gets the width of the component.		 */		override public function set width(w:Number):void {			if(w == _width) return;			_width = w;			invalidate(VALID_SIZE);		}		override public function get width():Number {			return _width;		}		/**		 * Sets/gets the height of the component.		 */		override public function set height(h:Number):void {			if(h == _height) return;			_height = h;			invalidate(VALID_SIZE);		}		override public function get height():Number {			return _height; 		}		/**		 * Measured height and width as returned by DisplayObject.height		 */		public function get actualHeight():Number {			return super.height;		}		/**		 * Measured width and width as returned by DisplayObject.width		 */		public function get actualWidth():Number {			return super.width;		}		/**		 * MXML DefaultProperty, receieves references to classes added as child nodes 		 * after this component has been initialized		 */		public function set children(value:Array):void {			for each(var o:* in value) {				var child:DisplayObject = (o as DisplayObject);				if(child!=null) {					// add to the display list					addChild(child as DisplayObject);										// use id as instance name (like flex) 					try { 						this[child['id']]=child; 					} catch (e:Error) {} // fail silently				} 			}				debug( getTimer() + ' set children');		}					/**		 * Added to stage hook		 */		protected function onAddedToStage(event:Event):void {						}	}}